今天,我們要來實作一個報表和分析功能,讓健身房老闆可以一目了然地看到整個健身房的運營狀況。
首先,我們需要安裝 Recharts,這是一個強大的 React 圖表庫:
npm install recharts
讓我們來更新我們的儀表板頁面,加入一些重要的統計數據和圖表。
在 src/pages/Dashboard.tsx
中:
import React from 'react';
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, BarChart, Bar, PieChart, Pie, Cell } from 'recharts';
interface DashboardData {
totalMembers: number;
totalClasses: number;
revenueThisMonth: number;
memberGrowth: { month: string; members: number }[];
classAttendance: { className: string; attendees: number }[];
membershipTypes: { type: string; count: number }[];
}
const fetchDashboardData = async (): Promise<DashboardData> => {
const { data } = await axios.get<DashboardData>('/api/dashboard');
return data;
};
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042'];
const Dashboard: React.FC = () => {
const { data, isLoading, error } = useQuery(['dashboardData'], fetchDashboardData);
if (isLoading) return <div>載入中...</div>;
if (error) return <div>發生錯誤:{(error as Error).message}</div>;
return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">儀表板</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
<Card>
<CardHeader>
<CardTitle>總會員數</CardTitle>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">{data?.totalMembers}</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>總課程數</CardTitle>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">{data?.totalClasses}</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>本月營收</CardTitle>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">${data?.revenueThisMonth}</p>
</CardContent>
</Card>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card>
<CardHeader>
<CardTitle>會員成長趨勢</CardTitle>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data?.memberGrowth}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="members" stroke="#8884d8" />
</LineChart>
</ResponsiveContainer>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>課程參與度</CardTitle>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={data?.classAttendance}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="className" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="attendees" fill="#82ca9d" />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>會員類型分佈</CardTitle>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie
data={data?.membershipTypes}
cx="50%"
cy="50%"
labelLine={false}
outerRadius={80}
fill="#8884d8"
dataKey="count"
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
>
{data?.membershipTypes.map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip />
</PieChart>
</ResponsiveContainer>
</CardContent>
</Card>
</div>
</div>
);
};
export default Dashboard;
接下來,我們來建立一個專門的報表頁面,提供更詳細的數據分析。
在 src/pages/Reports.tsx
中:
import React, { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
interface ReportData {
revenue: { date: string; amount: number }[];
newMembers: { date: string; count: number }[];
classAttendance: { date: string; count: number }[];
}
const fetchReportData = async (period: string): Promise<ReportData> => {
const { data } = await axios.get<ReportData>(`/api/reports?period=${period}`);
return data;
};
const Reports: React.FC = () => {
const [period, setPeriod] = useState('month');
const { data, isLoading, error } = useQuery(['reportData', period], () => fetchReportData(period));
if (isLoading) return <div>載入中...</div>;
if (error) return <div>發生錯誤:{(error as Error).message}</div>;
return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">報表</h1>
<div className="mb-4">
<Select value={period} onValueChange={setPeriod}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="選擇時間範圍" />
</SelectTrigger>
<SelectContent>
<SelectItem value="week">本週</SelectItem>
<SelectItem value="month">本月</SelectItem>
<SelectItem value="year">本年</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-1 gap-4">
<Card>
<CardHeader>
<CardTitle>營收趨勢</CardTitle>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data?.revenue}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="amount" stroke="#8884d8" />
</LineChart>
</ResponsiveContainer>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>新會員趨勢</CardTitle>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data?.newMembers}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="count" stroke="#82ca9d" />
</LineChart>
</ResponsiveContainer>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>課程參與度趨勢</CardTitle>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data?.classAttendance}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="count" stroke="#ffc658" />
</LineChart>
</ResponsiveContainer>
</CardContent>
</Card>
</div>
</div>
);
};
export default Reports;
最後,我們需要更新路由設置來包含新的報表頁面。在 src/App.tsx
中:
import React from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import MainLayout from './layouts/MainLayout';
import Dashboard from './pages/Dashboard';
import Members from './pages/Members';
import MemberDetail from './pages/MemberDetail';
import Classes from './pages/Classes';
import ClassCalendar from './pages/ClassCalendar';
import ClassDetail from './pages/ClassDetail';
import Reports from './pages/Reports';
import Login from './pages/Login';
function App() {
return (
<Router>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/" element={<MainLayout />}>
<Route index element={<Dashboard />} />
<Route path="members" element={<Members />} />
<Route path="members/:id" element={<MemberDetail />} />
<Route path="classes" element={<Classes />} />
<Route path="class-calendar" element={<ClassCalendar />} />
<Route path="classes/:id" element={<ClassDetail />} />
<Route path="reports" element={<Reports />} />
</Route>
</Routes>
</Router>
);
}
export default App;
今天為 Gym Pro 系統新增報表和分析功能,包括: